package org.tulkas.selector.pattern;

import org.tulkas.selector.Filter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public abstract class PseudoChild implements Filter {
	private final int a;
	private final int b;

	protected PseudoChild(int a, int b) {
		this.a = a;
		this.b = b;
	}

	public int getA() {
		return a;
	}

	public int getB() {
		return b;
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(getDisplayName()).append("(");
		String n = null;
		switch (a) {
		case 0:
			n = "";
			break;
		case 1:
			n = "n";
			break;
		case -1:
			n = "-n";
			break;
		default:
			n = String.valueOf(a) + "n";
		}
		String l = null;
		if (b == 0) {
			l = "";
		} else if (b > 0) {
			l = "+" + String.valueOf(b);
		} else {
			l = String.valueOf(b);
		}
		sb.append(n).append(l).append(")");
		return sb.toString();
	}

	protected abstract String getDisplayName();

	public static class Root extends PseudoChild {
		protected Root() {
			super(0, 0);
		}

		@Override
		protected String getDisplayName() {
			return "root";
		}

		@Override
		public String toString() {
			return getDisplayName();
		}

		@Override
		public int getPriority() {
			return PRIORITY_ID;
		}

		@Override
		public boolean matches(Element element) {
			Document doc = element.getOwnerDocument();
			if (doc != null) {
				return doc.getDocumentElement() == element;
			} else {
				return false;
			}
		}
	}

	public static class NthChild extends PseudoChild {
		protected NthChild(int a, int b) {
			super(a, b);
		}

		@Override
		protected String getDisplayName() {
			return "nth-child";
		}

		@Override
		public int getPriority() {
			return PRIORITY_PSEUDO_CHILD + 10;
		}

		@Override
		public boolean matches(Element element) {
			Node parent = element.getParentNode();
			if (parent == null) {
				return false;
			}
			Node node = parent.getFirstChild();
			int idx = 0;
			while (node != null) {
				if (node instanceof Element) {
					idx++;
					if (node == element) {
						break;
					}
				}
				node = node.getNextSibling();
			}
			if (getA() == 0) {
				return idx == getB();
			} else {
				int nx = idx - getB();
				return (nx % getA() == 0) && (nx / getA() > 0);
			}
		}
	}

	public static class NthLastChild extends PseudoChild {
		protected NthLastChild(int a, int b) {
			super(a, b);
		}

		@Override
		protected String getDisplayName() {
			return "nth-last-child";
		}

		@Override
		public int getPriority() {
			return PRIORITY_PSEUDO_CHILD + 10;
		}

		@Override
		public boolean matches(Element element) {
			Node parent = element.getParentNode();
			if (parent == null) {
				return false;
			}
			Node node = parent.getLastChild();
			int idx = 0;
			while (node != null) {
				if (node instanceof Element) {
					idx++;
					if (node == element) {
						break;
					}
				}
				node = node.getPreviousSibling();
			}
			if (getA() == 0) {
				return idx == getB();
			} else {
				int nx = idx - getB();
				return (nx % getA() == 0) && (nx / getA() > 0);
			}
		}
	}

	public static class NthOfType extends PseudoChild {
		protected NthOfType(int a, int b) {
			super(a, b);
		}

		@Override
		protected String getDisplayName() {
			return "nth-of-type";
		}

		@Override
		public int getPriority() {
			return PRIORITY_PSEUDO_CHILD + 20;
		}

		@Override
		public boolean matches(Element element) {
			Node parent = element.getParentNode();
			if (parent == null) {
				return false;
			}
			Node node = parent.getFirstChild();
			String tagName = element.getTagName();
			int idx = 0;
			while (node != null) {
				if ((node instanceof Element)
						&& tagName.equals(((Element) node).getTagName())) {
					idx++;
					if (node == element) {
						break;
					}
				}
				node = node.getNextSibling();
			}
			if (getA() == 0) {
				return idx == getB();
			} else {
				int nx = idx - getB();
				return (nx % getA() == 0) && (nx / getA() > 0);
			}
		}
	}

	public static class NthLastOfType extends PseudoChild {
		protected NthLastOfType(int a, int b) {
			super(a, b);
		}

		@Override
		protected String getDisplayName() {
			return "nth-last-of-type";
		}

		@Override
		public int getPriority() {
			return PRIORITY_PSEUDO_CHILD + 20;
		}

		@Override
		public boolean matches(Element element) {
			Node parent = element.getParentNode();
			if (parent == null) {
				return false;
			}
			Node node = parent.getLastChild();
			String tagName = element.getTagName();
			int idx = 0;
			while (node != null) {
				if ((node instanceof Element)
						&& tagName.equals(((Element) node).getTagName())) {
					idx++;
					if (node == element) {
						break;
					}
				}
				node = node.getPreviousSibling();
			}
			if (getA() == 0) {
				return idx == getB();
			} else {
				int nx = idx - getB();
				return (nx % getA() == 0) && (nx / getA() > 0);
			}
		}
	}

	public static class OnlyChild extends PseudoChild {
		protected OnlyChild(int a, int b) {
			super(a, b);
		}

		@Override
		protected String getDisplayName() {
			return "only-child";
		}

		@Override
		public int getPriority() {
			return PRIORITY_PSEUDO_CHILD;
		}

		@Override
		public String toString() {
			return getDisplayName();
		}

		@Override
		public boolean matches(Element element) {
			Node parent = element.getParentNode();
			if (parent == null) {
				return false;
			}
			Node node = parent.getFirstChild();
			int idx = 0;
			boolean first = false;
			while (node != null) {
				if (node instanceof Element) {
					idx++;
					if (idx == 1 && node == element) {
						first = true;
					}
					if (idx == 2) {
						return false;
					}
				}
				node = node.getNextSibling();
			}
			return first;

		}
	}

	public static class OnlyOfType extends PseudoChild {
		protected OnlyOfType() {
			super(0, 0);
		}

		@Override
		protected String getDisplayName() {
			return "only-of-type";
		}

		@Override
		public int getPriority() {
			return PRIORITY_PSEUDO_CHILD + 5;
		}

		@Override
		public String toString() {
			return getDisplayName();
		}

		@Override
		public boolean matches(Element element) {
			Node parent = element.getParentNode();
			if (parent == null) {
				return false;
			}
			Node node = parent.getFirstChild();
			String tagName = element.getTagName();
			int idx = 0;
			boolean first = false;
			while (node != null) {
				if ((node instanceof Element)
						&& tagName.equals(((Element) node).getTagName())) {
					idx++;
					if (idx == 1 && node == element) {
						first = true;
					}
					if (idx == 2) {
						return false;
					}
				}
				node = node.getNextSibling();
			}
			return first;

		}
	}
}
